Плохо! Плохо!:  0
Показано с 1 по 6 из 6

Тема: [MZ] Сборник рецептов для добавления «ЭкшОна» в игру (и некоторые размышления)

  1. #1
    Бывалый Аватар для Darchan Kaen
    Информация о пользователе
    Регистрация
    17.06.2013
    Адрес
    Одесса
    Сообщений
    851
    Записей в дневнике
    3
    Репутация: 47 Добавить или отнять репутацию

    По умолчанию [MZ] Сборник рецептов для добавления «ЭкшОна» в игру (и некоторые размышления)

    И снова здравствуйте.

    В данном тексте мне хотелось «закруглить» свое мини-исследование мукера, дать готовые к употреблению/модификации рецепты, и где-то немножко порассуждать (поныть то есть).

    Текст является чем-то средним между туториалом, сборником рецептов и статьей и состоит из нескольких частей:
    0. Предисловие
    1. Подготовительная часть (теоретическая и техническая информация).
    2. Практическая часть («сборник рецептов»).
    3. Нюансы по практической части.
    4. Послесловие

    Читать все их подряд не обязательно: например, если вы знаете, что из себя представляет RPG Maker как платформа, можно пропустить Теоретическую часть, а если знаете внутренние структуры мейкера ($gameMap, $dataMap, $dataItems, итд) – можно пропустить Техническую часть. Но нюансы все таки прочитайте, если будете применять «рецепты» - чтоб потом не удивляться.

    Приготовьтесь к тому, что многие вещи могут быть для вас очевидны и / или банальны. Буков будет много.

    Спойлер 0. Предисловие:

    Все нижеизложенное будет справедливо только для RPG Maker MV / MZ, т.к. на более ранних (и, наверное, более поздних) версиях мукера платформа и внутренний язык были / вероятно будут / другими.

    Сам текст носит уклон в практическую часть, а именно – применение ВСТРОЕННЫХ возможностей мейкера для реализации «не стандартных» для его создателей, но привычных уже для нас с вами игровых возможностей. Как, например: элементы Активной боевой системы (АБС), прыжки, двигающиеся платформы, шипы. Скриптами.

    Информацию, рассмотренную в данном тексте, вы можете модифицировать и комбинировать на ваше усмотрения, создавая механики, не изложенные в этом туториале.

    Вся указанная ниже информация является результатом моего «изучения» движка и субъективной интерпретации данных и не претендует на абсолютную истину. И я не могу сказать, что знаю мейкер досконально – есть люди, знающие намного лучше (VarVarKa, DK, Caveman, dirge, yuryol, Демий, votetot и многие, многие другие). Пусть они поправят меня, где я не прав.

    Источники информации:
    1) Ковыряние исходников интерпретатора мукера Блокнотом++
    2) Сторонняя документация по классам мукера (MV, на MZ очень похоже):
    https://kinoar.github.io/rmmv-doc-web/globals.html
    3) Перечень скриптовых команд MV/MZ:
    https://docs.google.com/spreadsheets...h7OHs/htmlview
    4) Гугл и ответы на разных форумах
    5) Ковыряние чужих плагинов (чуть-чуть)
    6) Уроки DK (жаль, я поздновато их увидел):
    https://rpgmaker.su/f97/%D0%A3%D1%80...D1%82-dk-3014/

    Что нужно для полного понимания изложенной информации:
    1) Самое базовое знание Computer Science – концепции «массивов», «объектов».
    2) Базовые знания JS, а именно: объявление переменных, условные операторы, массивы, объекты.

    И да, если вы почему-то хотите сделать ABS или еще что-то «экшоновое» на мейкере, использование готовых плагинов – самый оптимальный путь.

    Все примеры кода – проверялись на MZ, в MV теоретически тоже должны работать, но гарантировать это я не могу и не буду.
    Последний раз редактировалось Darchan Kaen; 30.03.2022 в 19:27.

  2. #2
    Бывалый Аватар для Darchan Kaen
    Информация о пользователе
    Регистрация
    17.06.2013
    Адрес
    Одесса
    Сообщений
    851
    Записей в дневнике
    3
    Репутация: 47 Добавить или отнять репутацию

    По умолчанию

    Спойлер 1. Подготовительная часть:

    1-1. Теоритическая информация
    Что такое RPG Maker MV / MZ? «Мукер – это мы»(с)
    Мейкер условно состоит из 2-х частей – Редактора и Интерпретатора (он же «проект», «игра»).

    Редактор, написан на QT и является +- нативными приложением (как я понял). Интерпретатор же представляет собой локальный веб-сервер со встроенным браузером и (относительно) много кода на JavaScript + файлы графики/
    музыки. При этом в роли «локального веб-сервера и встроенный браузера» одновременно выступает NW.js. Также есть варианты экспорта «игры» для своего сервера (без браузера и NW.js в комплекте) и для Android (на MV) – но эти возможности лежат вне темы туториала.

    Данная информация интересна больше для «плагинописетелей» (что плагинами при желании можно изменить минимум 80% движка, мы все знаем), но нам тоже полезно понимать, что игра на мукере это, по сути, браузерная игра. И что NW.js также включает в себя API Node.js, что позволяет нам работать с файлами операционной системы (читать, писать).

    То, что мы кликаем-клацаем в Редакторе, карты-эвенты-скилы, сохраняется в виде JSON-файлов, которые и интерпретирует Интерпретатор. При сильном желании и понимании процесса можно эти файлы править руками, например меняя диалоги, хар-ки оружия, итд. То есть, полностью зная работу Интерпретатора, можно сделать и свой Редактор с преферансом и поэтессами …и отгрести от злых японских юристов.

    Все это подводит к мысли, что как таковой мейкер – оболочка для запуска и работы, код JS да JSON-файлы с нашими данными и кнопочки редактора лишь удобная, в основном, над ним надстройка.

    Разработчики мейкера написали тысячи срок кода не совсем для красоты (и наших мучений), а предоставляя также некий внутренний «API» – перечень «внутренних» функций и игровых объектов – который, кроме редактора, можем использовать и мы. И именно их код и представляем собой «движок».

    Стоит понимать, что кроме API для игры и игрока, есть еще и «служебные» классы, отвечающие за логику сцен, отрисовку графики, проигрывание музыки, работу с файлами сохранений, тот же интерпретатор команд редактора – вот их из игры мы менять не можем (тут вступают в бой плагины…).

    То есть, кратко – любая кнопка редактора есть визуализация внутренней скриптовой команды. И писать код в некоторых ситуациях может быть удобнее, чем накликивать десятки эвентов (как минимум это часто быстрее работает).

    1-2. Техническая информация

    Под технической информацией имеется ввиду то самое «API» мейкера, которое нам («на лопате») предоставили его разрабы. Детальную информацию можно найти в уроках DK, в сторонней документации (ссылки в Предисловии), ну и открыв файлы движка блокнотом.

    С практической стороны, среди многих, нас интересуют следующие объекты и классы (а также их свойства и методы):
    1) Хранилище переменных - объект $gameVariables
    2) Хранилище переключателей - объект $gameSwitches
    3) Хранилище локальных переключателей - объект $gameSelfSwitches
    4) Объект игровой карты - $gameMap
    5) Объект с данными карты - $dataMap
    6) Объект эвента (за него отвечает класс Game_Event)
    7) Объект игрока $gamePlayer
    8) Объект экрана - $gameScreen
    9) Статический класс Scene
    10) Статический класс AudioManager

    После старта игры нам в любой момент как в игре, так и через консоль доступна работа с созданными игровыми объектами – например, в игре включить какой-то переключатель можно командой редактора или скриптовой командой $gameSwitches.setValue(n, true).

    Так как в практической части рассматривается использование эвентов на карте, нужно понимать, как соотносится видимая информация в редакторе и итоговая программная реализация в игре, а именно – эвенты разделают информацию в двух объектах, $gameMap и $dataMap.

    При этом, при инициализации карты сначала создаются «эвенты» в $dataMap, на основании чего движком уже создается массив эвентов в $gameMap.
    Это важно, так как эвенты в $dataMap содержат текст из Примечания эвента и в массиве эвентов нумерация идет по порядку, видимому в редакторе (не с нуля), а в массиве эвентов $gameMap – с нуля и нет инфы из Примечания.

    О самой же карте нам важно помнить, что она состоит из клеток, у каждой клетки есть координаты и может быть (или не быть) регион с номером.

    О эвентах важно знать, что у них есть свойства _x , _y, _direction и _eventId , отвечающие за координаты на экране, направление движения, Id (то есть номер) эвента, страницы и Примечание; координаты и направление также есть и у объекта игрока. И эти свойства можно менять вручную, перемещая эвент (или игрока) по экрану. Также важно помнить, что локальные переключатели эвентов хранятся не в объектах эвентов, а в объекте $gameSelfSwitches.

    Также нужно учитывать, что объект с переменными может хранить любой тип переменной – и текст, и массив, и функцию, и объект – а не только число. Что позволяет нам писать функцию и сохранять ее в переменной, используя своего-рода «распиленный» на куски плагин.

    Именно такой подход, сохранение функций в переменных, используется в практических примерах. Абсолютно безболезненно можно вынести это в плагин и вызывать его.

    Предпоследнее из теоретической части – нужно помнить, где именно можно вызывать скрипты в процессе игры и есть ли «привязка» к вызывающему скрипт эвенту. Вызывать скрипты можно в:
    1) Команде эвента «Скрипт» (вызывает без привязки в эвенту)
    2) В Задании маршрута эвента (вызывает с привязкой к эвенту)
    3) В Условии (вызывает без привязки к эвенту)

    Под «привязкой» подразумевается, что мейкер считает эвенты и «перечнем команд» и «объектами на карте», но не всегда одновременно в контексте вызова скрипта. То есть в Команде «Скрипт» и Условии this не указывает на объект эвента (указывает на объект Интерпретатора), а в Задании маршрута – указывает на объект текущего эвента.

    И последнее из теории – пример основных скриптовых команд, что будут применяться в практической части.
    Код:
    $gameVariables.setValue(1, () => {}); // создание функции внутри переменной мейкера.
    let myFunction = $gameVariables.value(1); // получение сохраненной функции.
    $gameSwitches.setValue(2, true); // включение переключателя.
    $gameSelfSwitches.setValue([$gameMap._mapId, event._eventId, ‘A’], false); // выключение локального переключателя «А» како-то эвента.
    $gameMap.events(); // получения массива всех эвентов карты.
    $gameMap.eventsXy(2, 3)[0]; // получение первого из эвентов, находящихся по заданным координатам.
    $gameMap.event(4); // получение эвента по id (от объекта карты).
    $dataMap.events; // получение массива data-эвентов (с Примечанием!).
    $dataMap.events[5].note; // получение примечания конкретного эвента.
    $gameTemp. reserveCommonEvent(6); // вызов Общего события.
    $gameScreen.startShake(7, 7, 15); // тряска экрана.
    event._x // получение координаты x какого-то эвента.
    $gamePlayer._y = 2 // задание координаты y для игрока.
    AudioManager.playSe({ name: 'Buzzer1', volume: 60, pitch: 100, pan: 0 });  // воспроизведение SE-звука.
    $gameSystem.onBeforeSave(); DataManager.saveGame(8); // для сохранения в указанный слот (нужны оба метода в такой последовательности).

  3. #3
    Бывалый Аватар для Darchan Kaen
    Информация о пользователе
    Регистрация
    17.06.2013
    Адрес
    Одесса
    Сообщений
    851
    Записей в дневнике
    3
    Репутация: 47 Добавить или отнять репутацию

    По умолчанию

    Спойлер 2. Практическая часть:

    Тут будет «сборник рецептов», представляющих собой функции, сохраненные в переменные мейкера. Часть из них можно использовать сами по себе, часть требует наличия других связанных функций. Повторюсь, при желании, все из этого можно вынести в отдельный плагин…по сути, это самый оптимальный вариант работы.

    Большая часть кода практически использовалась в игре “ALWBAW”, ее же можно рассматривать как демо-версию – игра не зашифрована, ее можно открыть редактором (подбросив файл проекта MZ); если будете открывать – особый интерес представляют первые три «технические» карты и Общие события. Игру можно найти на данном форуме, на Светлой полосе, на Юнионе.
    Если «рецепт» в реальной игре не тестировался – это будет указано отдельно.
    Демо-игра также использует логику «сопартиец-как-патрон», что в рамках данного туториала рассматриваться не будет.

    В существенной части рецептов используется применение скрипта в Маршруте эвента и тот факт, что на скорость исполнения маршрута влияет частота эвента. Также используется возможность хранения и записи текста в Примечании эвента. Кроме того, для ряда рецептов (прыжок, платформы) используется задание регионов на карте – во всех примерах корректным является регион с номером «1» (это тоже можно менять).

    У каждого эвента-врага специальным образом заполнено Примечание и две страницы – первая, активируемая лок. переключателем «A» активная и вторая, активируемая лок. переключателем «B» не активная (враг уничтожен).
    У эвентов-"пулек" и эвентов-"бомб" - три страницы, первая пустая не активная (но уже с нужной графикой!), вторая и третья как у эвентов-врагов

    Номера функций в примерах кода взяты из игры, вы можете ставить любые. Код приводится в «сжатом» варианте – гарантированно влезает в команду «Скрипт» на MZ. Скриншотов не будет.
    Код, как и логику что он реализует, может быть не оптимальна.

    Спойлер Рецепт №1. «Прыжок с проверкой допустимости конечной точки»:

    Цель:
    Игрок прыгает на N-клеток вперед в сторону, которую повернут лицом (это называется «направление» в контексте мейкера) и только в том случае, если конечная точка допустима для приземления (чтоб не оказаться в стене, яме, итд). Направление передается из вызывающего кода.

    Логика:
    Согласно направлению и дальности прыжка, нужно узнать является ли конечная точка допустимым местом приземления. Для этого, исходя из направления, рассчитываются координаты корректной точки и проверяется ее номер ее региона.
    В случае, если конечная точка нам подходит, то так как прыжок работает по разнице координат, мы получаем текущие координаты игрока, вычисляем разницу с конечными координатами и вызываем для игрока встроенную функцию прыжка по вычисленным значениям. В коде также вызывается дрожь экрана и SE-звук, причем отдельный звук в случаях прыжка и отказа.

    Код функции Прыжка:
    Код:
    $gameVariables.setValue(10, (direction) => {
    let jumpRadius = 3; let playerEndpointCoords = [];
    if(8 === direction){ playerEndpointCoords = [$gamePlayer._x, $gamePlayer._y - jumpRadius];
    }else if(6 === direction){ playerEndpointCoords = [$gamePlayer._x + jumpRadius, $gamePlayer._y];
    }else if(4 === direction){ playerEndpointCoords = [$gamePlayer._x - jumpRadius, $gamePlayer._y];
    }else if(2 === direction){ playerEndpointCoords = [$gamePlayer._x, $gamePlayer._y + jumpRadius];
    }
    let playerEndpointRegionId = $gameMap.regionId(playerEndpointCoords[0], playerEndpointCoords[1]);
    if(1 === playerEndpointRegionId){
    let jumpCoordX = playerEndpointCoords[0] - $gamePlayer._x;
    let jumpCoordY = playerEndpointCoords[1] - $gamePlayer._y;
    if(undefined !== jumpCoordX && undefined !== jumpCoordY){
    $gamePlayer.jump(jumpCoordX, jumpCoordY);
    $gameScreen.startShake(7, 7, 15); AudioManager.playSe({ name: 'Earth1', volume: 60, pitch: 100, pan: 0 });
    }else{ AudioManager.playSe({ name: 'Buzzer1', volume: 60, pitch: 100, pan: 0 }); }
    }else{ AudioManager.playSe({ name: 'Buzzer1', volume: 60, pitch: 100, pan: 0 }); }
    });
    Что можно улучшить:
    Можно задать динамическую дальность прыжка, храня «радиус» в отдельной переменной. Можно сделать так, что в случае некоррктности конечной клетки, проверяется корректность предыдущий…и игрок прыгает хоть как-то. Можно хранить стартовые (до-прыжковые) координаты игрока в отдельной переменной.


    Спойлер Рецепт №2. «Клонирование эвента с его последующей активацией»:

    Цель:
    Создать новый эвент программно, по образцу уже существующего. При этом существующий эвент «не активен», т.е. первая страница пуста, а для работы он требует включеный локальный переключатель «A». При этом новый эвент создается по конкретным координатам с конкретным направлением. Id эвента для копирования, необходимые координаты и направление передаются из вызывающего кода.

    Логика:
    Копируем эвент, задаем ему новый случайный Id (цифры 1000 и 500 произвольны и исходят из кол-во ОБЫЧНЫХ, не клонированных, эвентов на карте), вставляем его в массивы эвентов игровые объектов $dataMap и $gameMap (для второго предварительно нужно создать новый «игровой» эвент), задаем новому эвенту направление, перемещаем в координаты, включаем у него «активный» локальный переключатель и обновляем графику всех эвентов на карте (сцене), тем самым делая клонированный эвент видимым.

    Код функции Клонирования:
    Код:
    $gameVariables.setValue(5, (eventToCloneId, cloneInCoordX, cloneInCoordY, cloneInDirection) => {
    let eventToCloneData = $dataMap.events[eventToCloneId]; 
    let clonedEventData = {...eventToCloneData};
    let newId = eventToCloneId + Math.floor(Math.random() * 1000 + 500); 
    if($dataMap.events.length < newId){ $dataMap.events.length = newId; }
    $dataMap.events[newId] = clonedEventData;
    let clonedMapEvent = new Game_Event(this._mapId, clonedEventData.id);
    clonedMapEvent._direction = cloneInDirection;
    clonedMapEvent.locate(cloneInCoordX, cloneInCoordY);
    $gameMap._events.push(clonedMapEvent);
    $dataMap.events[$dataMap.events.length - 1].id = newId; 
    $gameMap._events[$gameMap._events.length - 1]._eventId = newId;
    $gameSelfSwitches.setValue([$gameMap._mapId, newId, 'A'], true);
    SceneManager._scene._spriteset.createCharacters();
    });
    Что можно улучшить:
    Вероятно, можно улучшить генерацию случайного Id для нового эвента, введя доп. проверки на наличие таких Id.


    Спойлер Рецепт №3. «Понижение ХП игрока, урон по игроку»:

    Цель:
    Программного снижать ХП игрока (точнее, первого персонажа-лидера, в партии).

    Логика:
    Получаем объект игрока (лидера партии), меняем ХП с разрешением смерти.

    Код функции Снижения ХП:
    Код:
    $gameVariables.setValue(9, () => {
    let player = $gameParty.leader();
    let decrValue = -1;
    let canKO = true;
    this.changeHp(player, decrValue, canKO);
    });
    Что можно улучшить:
    Можно сделать, что бы величина уменьшения ХП передавалась в функцию снаружи и не была константой.


    Спойлер Рецепт №4. «Шипы»:

    Цель:
    Получить эвент, циклически меняющий свое состояние (фазы) и графику, при некоторых состояниях себя наносящий урон игроку, что находится на эвенте или проходит через него…т.е. наши любимые шипы. Желательно – без параллельных событий и «мучений» с графикой и маршрутом эвента.

    Логика:
    Эвент под персонажем, фиксированный.
    У эвента четыре страницы (они же стостояния-фазы), первая не требует локальные переключатели, следующие требуют включения «A», «B», «C».
    У эвента нормальная скорость и максимальная частота, у каждой страницы своя графика шипов.
    У каждой страницы задан маршрут, а именно:
    1) Первая страница – ждать 30 секунд и выполнить скрипт для включения лок. Переключателя «A».
    2) Вторая страница – ждать 30 секунд и выполнить скрипт для включения лок. Переключателя «B» с выключением лок. переключателя «A».
    3) Третья страница – выполнить код урона шипов для маршрута, ждать 30 секунд и выполнить скрипт для включения лок. переключателя «C» с выключением лок. переключателей «A» и «B».
    4) Четвертая страница – выполнить код урона шипов для маршрута, ждать 30 секунд и выполнить скрипт для выключения лок. переключателей «A», «B» и «C».
    Третья и четвертые страницы, кроме маршрута, активируются от касания игрока и выполняют специальный скрипт (урон игроку и показ анимации).
    Логика скрипта в маршруте – проверка координат игрока и если совпадают с координатами эвента, то урон игроку и анимация.

    Код в маршрурте, для переключения страницы-состояния (маршрут Повторяется, код должен быть одной строкой - но форум "поломал"):
    Код:
    //Первая страница:
    $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'A'], true);
    //Вторая страница:
    $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'A'], false); $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'B'], true);
    //Третья страница:
    $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'A'], false); $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'B'], false); $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'C'], true);
    //Четвертая страница:
    $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'A'], false); $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'B'], false); $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'C'], false);
    Код в маршруте эвента, на фазах урона (3 и 4 страницы):
    Код:
    let spikeDamage = $gameVariables.value(14); spikeDamage(this);
    Код Урона шипов для 3-й и 4-й страниц эвента:
    Код:
    let damagePlayer = $gameVariables.value(9); 
    $gameTemp.requestAnimation([$gamePlayer], 1);
    damagePlayer();
    Код функции Урона шипов для маршрута:
    Код:
    $gameVariables.setValue(14, (spikeEvent) => {
    if(spikeEvent._x === $gamePlayer._x && spikeEvent._y === $gamePlayer._y){ 
    let damagePlayer = $gameVariables.value(9); 
    $gameTemp.requestAnimation([$gamePlayer], 1);
    damagePlayer(); 
    }
    });
    Что можно улучшить:
    Не знаю, может вы подскажите.


    Спойлер Рецепт №5. «Движущиеся платформы»:

    Цель:
    Движущаяся эвент-платформа, на которую игрок может стать, и которая «красиво» транспортирует игрока в заданную точку.

    Логика:
    Эвент под персонажем.
    У эвента две страницы, в маршруте одной задается движение в нужную сторону, в маршруте другой – возврат к исходной точке; вторая страница включается лок. переключателем «A».
    Скорость нормальная, частота максимальная, графика на страницах может быть одинаковая / разная.
    Маршрут (маршрут Повторяется):
    1) Выключается проходимость.
    2) Ожидание 120 фреймов.
    3) Включается проходимость.
    4) …ставится нужное кол-во шагов, для достижения конечной точки…
    5) Выполняется скрипт для включения / выключения лок. переключателя «A» (в зависимости от страницы).
    Также каждая страница, кроме маршрута, активируется от касания игрока и выполняет скрипт.
    Логика скрипта на странице эвента – получаем объект эвента текущей платформы, задаем его координаты, задаем Схему движения, вызываем Функцию платформы и передаем ей это как аргументы.
    Логика функции Платформы – запрет прыжка, определение маршрута движения исходя из Схемы движения, движение пока не платформа не коснется корректного региона (под номером «1»), прыжок игрока на клетку в которую уперлась платформа, включение прыжка.

    Код в маршруте, для переключения страницы:
    Код:
    //Первая страница
    $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'A'], true);
    //Вторая страница
    $gameSelfSwitches.setValue([$gameMap._mapId, this._eventId, 'A'], false);
    Код в странице эвента:
    Код:
    let movePlatformWithPlayer = $gameVariables.value(13);
    let currentPlatform = $gameMap.event(5);
    let originCoords = [7, 8];
    let movePattern = 'DOWN-UP';
    movePlatformWithPlayer(currentPlatform, originCoords[0], originCoords[1], movePattern);
    Код функции Платформы для страницы эвента:
    Код:
    $gameVariables.setValue(13, (platformEvent, originX, originY, movePattern) => {
    $gameSwitches.setValue(2, false);
    let shiftByPatternX = 0; let shiftByPatternY = 0;
    if('UP-DOWN' === movePattern){ shiftByPatternY = 1;
    }else if('DOWN-UP' === movePattern){ shiftByPatternY = -1;
    }else if('LEFT-RIGHT' === movePattern){ shiftByPatternX = 1;
    }else if('RIGHT-LEFT' === movePattern){ shiftByPatternX = -1;
    }
    let nextTileX = platformEvent._x + shiftByPatternX; let nextTileY = platformEvent._y + shiftByPatternY; 
    let nextTileRegionId = $gameMap.regionId(nextTileX, nextTileY); 
    $gamePlayer._walkAnime = false;
    if(1 !== nextTileRegionId){
    platformEvent._x += shiftByPatternX; platformEvent._y += shiftByPatternY; 
    $gamePlayer._x += shiftByPatternX; $gamePlayer._y += shiftByPatternY;
    }else{
    $gamePlayer.jump(shiftByPatternX, shiftByPatternY);
    $gamePlayer._walkAnime = true;
    platformEvent._x = originX; platformEvent._y = originY;
    $gameSwitches.setValue(2, true);
    }
    });
    Что можно улучшить:
    В принципе, хотелось бы отвязаться от ручного задания координат в скрипте эвента – но способа я не нашел.


    Спойлер Рецепт №6. «Жив ли враг»:

    Цель:
    Узнать, является ли целевой эвент врагом и если да – есть у него еще ХП. Id врага-эвента задается извне.

    Логика:
    В каждом эвенте, который мы хотим использовать в качестве «врага» в своей АБС, в примечании пишем текст:
    {"type": "ENEMY", "hp": 3, "countVarId": 26}
    То есть, задаем объект с характеристиками:
    type – тип;
    hp – хп;
    countVarId – номер пеменной, в которую плюсуется +1 при уничтожении врага.
    Логика кода проверки – получаем Примечание нужного эвента (по его Id), парсим и читаем Примечание, находим и сравниваем ХП. Если ХП ноль и менее, или тип врага не тот, возвращаем false; иначе – true.

    Код функции Проверки ХП врага:
    Код:
    $gameVariables.setValue(2, (enemyEvent) => {
    let enemyData = $dataMap.events[enemyEvent._eventId].note; 
    if(0 === enemyData.length){ return false; }
    let enemyDataObj = JSON.parse(enemyData);
    let enemyHp = parseInt(enemyDataObj.hp);
    if(enemyHp <= 0 || 'ENEMY' !== enemyDataObj.type){
    return false;
    }else{
    return true;
    }
    });
    Что можно улучшить:
    Если типов врагов много, можно сделать доп. проверки.


    Спойлер Рецепт №7. «Уменьшение ХП врага»:

    Цель:
    Уменьшение характ-ки ХП эвента-врага. Id врага-эвента задается извне, как и урон.

    Логика:
    Получаем Примечание нужного эвента (по его Id), парсим и читаем Примечание, уменьшаем ХП, записываем обратно в Примечание эвента.
    Если ХП в результате меньше 0, то показываем на врага анимацию, включаем его «побежденный» переключатель «B», делая его тем самым неактивным, и приплюсовываем к переменной для кол-ва уничтоженных врагов единицу (если номер переменной равен 0, он игнорируется мейкером).
    ХП возвращается из ф-ии, т.к. это нужно для "бомбы".

    Код функции Уменьшения ХП врага:
    Код:
    $gameVariables.setValue(3, (enemyEvent, damageValue = 1) => {
    let enemyData = $dataMap.events[enemyEvent._eventId].note;
    if(0 === enemyData.length){ return false; }
    let enemyDataObj = JSON.parse(enemyData);
    let enemyHp = parseInt(enemyDataObj.hp);
    if(0 === enemyHp ){ return false; } 
    enemyHp -= damageValue;
    enemyDataObj.hp = enemyHp;
    $dataMap.events[enemyEvent._eventId].note = JSON.stringify(enemyDataObj);
    if(enemyHp <= 0){ 
    if('ENEMY' === enemyDataObj.type){ $gameTemp.requestAnimation([enemyEvent], 58); }
    $gameSelfSwitches.setValue([$gameMap._mapId, enemyEvent._eventId, 'B'], true);
    let countVarId = parseInt(enemyDataObj.countVarId); $gameVariables.setValue( countVarId, ($gameVariables.value(countVarId) + 1) );
    }
    return enemyHp; 
    });
    Что можно улучшить:
    Проверку по ХП. Возможно, добавить в «объект врага» кроме переменной – включение какого-то переключателя, это может быть полезно для Боссов.


    Спойлер Рецепт №8. «Удар игроком врага в ближнем бою»:

    Цель:
    Возможность для игрока атаковать эвент, находящийся в небольшой радиусе с ним, уменьшая его ХП.

    Логика:
    Логика делится на две части:
    1) Получения массива координат, в которых будет проходить поиска врагов; это и есть дальность и «радиус» удара; тут он не только перед игроком, но еще и по диагонали влево и вправо – для удобства. Массивы высчитывается исходя их координат игрока и его направления.
    2) По найденному массиву координат проходимся циклом, записываем его в массив найденных врагов. После чего проходимся уже по нему и для каждого найденного врага, проверяем жив ли он, и если да – наносим урон по эвенту-врагу и проигрываем анимацию.
    Перед вызовом скрипта с кодом атаки в ближнем бою, опционально, можно проиграть анимацию средствами команд мейкера.

    Код атаки врага в ближнем бою:
    Код:
    let getCoordsList = $gameVariables.value(1); let isEnemyyAlive = $gameVariables.value(2)
    let changeEnemyHP = $gameVariables.value(3);
    let coordsList = getCoordsList($gamePlayer); 
    let enemiesList = [];
    for(let n = 0; n < coordsList.length; n++){
    let currentEnemy = $gameMap.eventsXy(coordsList[n][0], coordsList[n][1]);
    if(currentEnemy.length > 0){
    enemiesList.push(currentEnemy[0]);
    }
    } 
    enemiesList.forEach(enemy => { 
    if(true === isEnemyyAlive(enemy)){
    $gameTemp.requestAnimation([enemy], 1); 
    changeEnemyHP(enemy); 
    }
    });
    Код функции Получения массива координат перед игроком:
    Код:
    $gameVariables.setValue(1, (player) => {
    let direction = player._direction;
    let coordsList = [];
    if(8 === direction){
    coordsList = [ [player._x - 1, player._y - 1], [player._x, player._y - 1], [player._x + 1, player._y - 1] ];
    }else if(6 === direction){
    coordsList = [ [player._x + 1, player._y - 1], [player._x + 1, player._y], [player._x + 1, player._y + 1] ];
    }else if(4 === direction){
    coordsList = [ [player._x - 1, player._y - 1], [player._x - 1, player._y], [player._x - 1, player._y + 1] ];
    }else if(2 === direction){
    coordsList = [ [player._x - 1, player._y + 1], [player._x, player._y + 1], [player._x + 1, player._y + 1] ];
    }
    return coordsList;
    });
    Что можно улучшить:
    Можно сделать несколько видов оружия ближнего боя и, в зависимости от выбранного оружия, менять массив координат для урона (оружие бьет вокруг, бьет с дальностью 2 клетки, бьет букой «Г», итд).



    Спойлер Рецепт №9. «Удар врагом игрока в ближнем бою»:

    Цель:
    Эвент-враг атакует игрока, находящегося рядом, понижая его ХП.

    Логика:
    Логика схожа с логикой удара игроком эвента.
    Сначала, исходя из координат атакующего эвента, получаем массив возможных координат атаки (функция в Рецепте №9).
    После чего для каждого варианта координат сверяем, совпадают ли они с координатами игрока и если да, то проигрываем на игрока анимации и снижаем его ХП. Опционально, в зависимости от направления эвента, проигрывается анимация «взмаха» оружием.
    Все это делает эвент-враг функцией, которую вызываем в маршруте (например: повторяющиеся шаг к игроку и вызов функции).
    Мне показалось, что оптимальная частота здесь не максимальная, а высокая.

    Код для маршрута:
    Код:
    let meleeEnemyAttack = $gameVariables.value(11); meleeEnemyAttack(this);
    Код функции Атаки врагом игрока в ближнем бою:
    Код:
    $gameVariables.setValue(11, (enemyEvent) => {
    let getCoordsList = $gameVariables.value(1);
    let damagePlayer = $gameVariables.value(9);
    let coordsList = getCoordsList(enemyEvent);
    let direction = enemyEvent._direction;
    for(let n = 0; n < coordsList.length; n++){
    if(coordsList[n][0] === $gamePlayer._x && coordsList[n][1] === $gamePlayer._y){
    if(8 === direction){ $gameTemp.requestAnimation([enemyEvent], 122); 
    }else if(6 === direction){ $gameTemp.requestAnimation([enemyEvent], 125); 
    }else if(4 === direction){ $gameTemp.requestAnimation([enemyEvent], 124); 
    }else if(2 === direction){ $gameTemp.requestAnimation([enemyEvent], 123); 
    }
    $gameTemp.requestAnimation([$gamePlayer], 1); 
    damagePlayer();
    }
    } 
    });
    Что можно улучшить:
    Если будет несколько типов врагов ближнего боя, для них можно сделать разные массивы координат (разные дальность и радиус удара).



    Спойлер Рецепт №10. «Выстрел игрока и/или врага»:

    Цель:
    При активации скрипта, перед игроком / врагом появляется эвент-«пулька» и движется N-ое количество шагов по направлению игрока и / или врага.

    Логика:
    Логика делится на логику «выстрела» и логику «пульки»
    Логика выстрела - получаем координаты клетки перед игроком / врагом, исходя из его направления, в которые будет перемещено клонируемый эвент-«пулька» и клонируем эвент-«пульку».
    Логика «пульки» - проходимый фиксированный эвент, с Примечанием {"type": "BULLET", "hp": 7, "countVarId": 0} , в котором ХП обозначает дальность движения. Страница «A» активна, на ней задан маршрут с высокой скоростью и максимальной частотой. В маршруте только выполняется скрипт. Логика функции вызываемого скрипта – каждый шаг «пульки» проверяется, есть ли на ее текущих координатах эвенты и игрок, если есть – анимация на них и им урон, и в любом случае – урон самой «пульке»!

    Код функции Клонирования эвента-«пульки»:
    Код:
    const EVENT_TO_SPAWN_ID = $gameVariables.value(27);
    let getFaceSideCoords = $gameVariables.value(4);
    let spawnEvent = $gameVariables.value(5); 
    
    let direction = $gamePlayer._direction; // для стреляющего врага тут указывается его направление!
    let coordsForSpawn = getFaceSideCoords($gamePlayer); //для стреляющего врага тут указывается его эвент!!
    
    spawnEvent(EVENT_TO_SPAWN_ID, coordsForSpawn[0], coordsForSpawn[1], direction);
    Код функции Получения координат перед игроком / врагом:
    Код:
    $gameVariables.setValue(4, (playerOrEvent) => {
    let coords = [];
    let direction = playerOrEvent._direction;
    if(8 === direction){
    coords[0] = playerOrEvent._x;
    coords[1] = playerOrEvent._y - 2;
    }else if(2 === direction){
    coords[0] = playerOrEvent._x;
    coords[1] = playerOrEvent._y + 2;
    }else if(4 === direction){
    coords[0] = playerOrEvent._x - 2;
    coords[1] = playerOrEvent._y;
    }else if(6 === direction){
    coords[0] = playerOrEvent._x + 2;
    coords[1] = playerOrEvent._y;
    } 
    return coords
    });
    Код для маршрута «пульки»:
    Код:
    let moveBulletEvent = $gameVariables.value(6); moveBulletEvent(this);
    Код функции Движения «пульки»:
    Код:
    $gameVariables.setValue(6, (bulletEvent) => { let isEnemyAlive = $gameVariables.value(2); let decreaseHp = $gameVariables.value(3);
    let enemiesOnWay = $gameMap.eventsXy(bulletEvent._x, bulletEvent._y);
    if(enemiesOnWay.length > 1){
    let enemyOnWay = enemiesOnWay.filter(enemy => enemy._eventId !== bulletEvent._eventId)[0]; 
    if(true === isEnemyAlive(enemyOnWay)){ $gameTemp.requestAnimation([enemyOnWay], 1); } 
    decreaseHp(enemyOnWay, 2); $gameSelfSwitches.setValue([$gameMap._mapId, bulletEvent._eventId, 'B'], true); 
    }
    if(bulletEvent._x === $gamePlayer._x && bulletEvent._y === $gamePlayer._y){ $gameTemp.requestAnimation([$gamePlayer], 1); 
    let damagePlayer = $gameVariables.value(9); damagePlayer();
    $gameSelfSwitches.setValue([$gameMap._mapId, bulletEvent._eventId, 'B'], true); 
    } decreaseHp(bulletEvent);
    let direction = bulletEvent._direction; 
    if(8 === direction){ bulletEvent._y -= 1;
    }else if(2 === direction){ bulletEvent._y += 1;
    }else if(4 === direction){ bulletEvent._x -= 1;
    }else if(6 === direction){ bulletEvent._x += 1;
    }
    });
    Что можно улучшить:
    Если будет несколько видом дальнобойного оружия, можно изменить проходимость (в данном коде «пулька» самоуничтожается после дамага по игроку / врагу), можно сделать несколько эвентов-«пулек» с разной дальностью (ХП) и скоростью. Возможно, копировать «пульку» не перед лицом стреляющего, а в других координатах (над, под, 2-3 пульки).


    Спойлер Рецепт №11. «Тикающая бомба»:

    Цель:
    При активации скрипта, на месте игрока / врага создается эвент-бомба, «взрывающийся» через некоторое количество времени.

    Логика:
    Логика схожа с логикой выстрела, т.к. для клонирования используется специальный объект-«часовая бомба», у которой тоже маршрут со скриптом, и которая тоже наносит себе же урон. При достижении 1 ХП и происходит «взрыв», нанося урон всем (и игроку, и эвентам-врагам) в заданном радиусе.
    А еще бобма прыгает в процессе "тикания".

    Код для «призыва бомбы»:
    Код:
    $gameVariables.setValue(15, (enemyEvent) => {
    const EVENT_TO_SPAWN_ID = $gameVariables.value(28);
    let getFaceSideCoords = $gameVariables.value(4);
    let spawnEvent = $gameVariables.value(5); 
    let direction = enemyEvent._direction;
    let coordsForSpawn = getFaceSideCoords(enemyEvent);
    spawnEvent(EVENT_TO_SPAWN_ID, coordsForSpawn[0], coordsForSpawn[1], direction);
    AudioManager.playSe( { name: 'Bite', volume: 90, pitch: 100, pan: 0 } );
    });
    Код для маршрута «бомбы»:
    Код:
    let tickBombEvent = $gameVariables.value(7); tickBombEvent(this);
    Код функции Тикания «бомбы»:
    Код:
    $gameVariables.setValue(7, (bombEvent) => { let isEnemyAlive = $gameVariables.value(2); let decreaseHp = $gameVariables.value(3);
    let hp = decreaseHp(bombEvent); bombEvent.jump(0, 0); 
    let bombRadius = [ [bombEvent._x - 1, bombEvent._y - 1], [bombEvent._x, bombEvent._y - 1], [bombEvent._x + 1, bombEvent._y - 1],
    [bombEvent._x - 1, bombEvent._y], [bombEvent._x, bombEvent._y], [bombEvent._x + 1, bombEvent._y],
    [bombEvent._x - 1, bombEvent._y + 1], [bombEvent._x, bombEvent._y + 1], [bombEvent._x + 1, bombEvent._y + 1], ];
    let enemiesList = [];
    if(1 === hp){
    $gameTemp.requestAnimation([bombEvent], 66); 
    for(let n = 0; n < bombRadius.length; n++){
    let currentEnemy = $gameMap.eventsXy(bombRadius[n][0], bombRadius[n][1]);
    if(currentEnemy.length > 0){
    enemiesList.push(currentEnemy[0]);
    }
    if(bombRadius[n][0] === $gamePlayer._x && bombRadius[n][1] === $gamePlayer._y){
    let damagePlayer = $gameVariables.value(9); damagePlayer();
    $gameTemp.requestAnimation([$gamePlayer], 1); 
    }
    } console.log('enemiesList:', enemiesList);
    enemiesList.forEach(enemy => { 
    if(true === isEnemyAlive(enemy)){
    $gameTemp.requestAnimation([enemy], 1); 
    decreaseHp(enemy, 3); 
    }
    });
    }
    });
    Что можно улучшить:
    Не знаю, мне просто нравится как это работает.


    Спойлер Рецепт №12. «Вывод информации»:

    А тут не будет рецепта, т.к. с графической частью я так и не разобрался.

    Для этого лучше использовать плагин: dirge, Galv, Orange…
    Я же в демо-игре просто в переменные записывал ХП, патроны и выбранное оружие и показывал в сообщении (ну и цветом экрана + графикой сопартийцев).
    Как справедливо заметил Петр, ставить параллельное событие с изменением цвета экрана – не очень мудро…но я так и делал. =)

    Чтоб рецепт не был пустым, вот то единственное что я смог найти и сделать для отрисовки окон изнутри мукера:
    Код:
    var rect = new Rectangle(25, 25, 400, 400);
    var win = new Window_Base(rect);
    SceneManager._scene.addChild(win);
    win.drawTextEx('\\C[18]HP\\V[24]', 125, 150, 350, 'center');
    Может быть, вы это сможете довести до вменяемого вида и как-то использовать.
    В реальной игре это не тестировалось.


    Спойлер Рецепт №DKR. «Бонус»:

    1) Изменение графики эвента:
    Код:
    $gameMap.event(n).setImage(graphicImage, graphicIndex);
    где graphicImage – название файла в кавычках без расширения, graphicIndex – номер графики персонажа в файле; нумерация начинается с 0.

    2) Синхронизация движения эвентов A и B (количество эвентов не ограничено), чтоб двигались по одинаковому маршруту:
    Код:
    let eventA = $gameMap.event(1);
    let eventB = $gameMap.event(2);
    let moveRouteA = eventA._moveRoute;
    eventB.forceMoveRoute(eventA._moveRoute);
    1, 2 - номера эвентов.
    В реальной игре это не тестировалось.


    Что еще можно сделать:
    «Не вошедшее»
    Бумеранг. Отражение «пулек» обратно в точку их старта. Телепорт-«крюк».
    И все, на что вам хватит фантазии и силы в борьбе с мукером.

    Последний раз редактировалось Darchan Kaen; 31.03.2022 в 09:06. Причина: Дополнение о страницах эвентов-врагов

  4. #4
    Бывалый Аватар для Darchan Kaen
    Информация о пользователе
    Регистрация
    17.06.2013
    Адрес
    Одесса
    Сообщений
    851
    Записей в дневнике
    3
    Репутация: 47 Добавить или отнять репутацию

    По умолчанию

    Спойлер 3. Нюансы по практической части:

    Что ж, сделать можно много чего, на самом то деле. Но чтоб работало это все вменяемо…может и не работать.
    Вот что нужно учесть при реализации всего вышенаписанного:

    1) Хоть мейкер и позволяет хранить функции в переменных, но «вешается», если таких функций будет много. Много, в моем случае, было равно 16. Пришлось делить объявление функций на три отдельных общих событий.
    Что значит «вешается»? Перестает подгружать-применять изменение в коде функций. Может он их так хитро кэширует…но иначе я это побороть не смог.

    2) Если у вас в игре есть перемещения по разным картам, то каждый раз после попадания игрока на карту с такой АБС, нужно заново вызывать событие инициализации всех функций. Поэтому событие с инициализацией (или которое вызываем Общее событие инициализации) нужно на карте ставить с «Удалением события» - это заставит его однократно отработать при КАЖДОМ перемещении на карту.

    3) При большом количестве эвентов-врагов, особенно стреляющих-бомбистов, может лагать. Ограничивайте их кол-во или активируйте их при достижении определенного фрагмента карты.

    4) Также при большом кол-ве эвентов может повторяться Id клонируемых, чем это чревато – я не знаю.

    5) При использовании такого подхода в создании «АБС», может быть ситуация что эвент «удалится» быстрее чем нужно и будет игровая ошибка. Я не нашел решения этого вопроса, кроме как написание отдельного мини-плагина, оборачивающего ошибку (код плагина скопируйте в файл с расшерением .js и подключите к проекту):

    Код:
    Game_Event.prototype.findProperPageIndex = function() {
    	try{
    		const pages = this.event().pages;
    		for (let i = pages.length - 1; i >= 0; i--) {
    			const page = pages[i];
    			if (this.meetsConditions(page)) {
    				return i;
    			}
    		}
    	}catch(exception){
    		return -1;
    	}
    	return -1; 
    };
    6) Ну и самый важный нюанс!
    Вызов меню (открытие любой другой сцены) заставляет мукер перерисовывать (или как-то иначе заново инициализировать) игровую сцену, в результате чего выходят очень интересные ошибки.
    Возможно, если реализовывать это все плагином - такого не будет.
    В текущей реализации единственный выход - запрещать игроку открывать все окна/сцены...то есть запрещать доступ к меню!

    Последний раз редактировалось Darchan Kaen; 30.03.2022 в 19:36. Причина: Самый важный нюанс!

  5. #5
    Бывалый Аватар для Darchan Kaen
    Информация о пользователе
    Регистрация
    17.06.2013
    Адрес
    Одесса
    Сообщений
    851
    Записей в дневнике
    3
    Репутация: 47 Добавить или отнять репутацию

    По умолчанию

    Спойлер 4. Послесловие:

    То есть нытье, субъективное.

    Как я уже сказал, кроме того, что нам заботливо предлагает редактор с кнопками, сделать можно много чего, даже без плагинов. С плагинами, конечно, еще больше.

    Встроенный API мейкера невелик, кривоват, неочевиден (особенно в графической части)…для меня, во всяком случае.

    Так что создание каких-то «нестандартных» для мукера механик, это отдельный вид искусства, где не благодаря, а скорее вопреки.
    Как говорит один мой друг – «мазохизм»(с)

    И я надеюсь, что если Enterbrain / Kadokawa / Degica / кто сейчас владеет правами на серию /, будут развивать мейкер, они таки добавят больше универсальности в движок, увеличат хотя-бы в 2 раза поле ввода «Скрипта» и сделают более человеческий API.
    А то в текущем состоянии движок похож на горизонт – манит «мнимой» полу-универсальностью, которую почти невозможно поймать за хвост.
    И даже при таком положении дел некоторые делают почти невозможное, правильно комбинируя возможности движка, плагины и код (например, шутер «ALL CAN DIE»).

    Но для своих нужд и официальных целей («rpg-игра без знания программирования») – все круто, Rpg Maker тащит.

    Что для меня самое лучше в мукере? Кроме сотен плагинов…Комьюнити.
    Комьюнити это лучшее, что есть у мейкера и за что его можно любить.



    Спасибо за внимание и удачи нам всем в это нелегкое время.

    Благодарности, без кого это мессиво текста было бы невозможным:

    Комьюнити rpgmaker.su, rpgmaker.ru, rpgmakerunion.ru (практически, это одни и те же люди).

    votetot – в одной из его игр, что была на RTP-2020 (название забыл, каюсь), меня впечатлило мастерство работы с движком и его графической частью…то был Vx Ace и это было круто. Да и другие его игры тоже круты, минимум в этом плане.

    Phileas – за идею работы над плагинами…и еще одну.

    Alx_Yago и Seibur – за идеи плагинов и помощь в реализации.

    Посос – за механику головы-бумеранга в одной из игр («Nailed School»), мне стало интересно как это сделано, я подсмотрел…и понял, что таки можно сделать что-то нестандартной «из-коробки». Забыл, а потом как вспомнил.

    Петр и Torontino - за идею конкурса "без плагинов", что заставила вспомнить о наличии команды "Скрипт" и вспомнить голову из пред. пункта; ну и первого также - за туториал по "Шипам", где параллельное событие используется (вроде он делал).
    Последний раз редактировалось Darchan Kaen; 30.03.2022 в 19:32.

  6. #6

    По умолчанию

    Для увеличения поля ввода «Скрипта» можно прогой ResizeEnableRunner пользоваться, она не связана с мукером, но может растягивать любые окна в винде(если делать что-то для ртп конкурса наверно не пойдёт, но для личного пользования самое то)

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Метки этой темы

Социальные закладки

Социальные закладки

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •  
[MZ] Сборник рецептов для добавления «ЭкшОна» в игру (и некоторые размышления)